import javax.swing.*;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.regex.*;
import java.util.Date;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public class BrikPanel extends JPanel implements MouseListener, ActionListener {
	Screen currScreen;
	Screen scr1, scr2, scr3, scr4;
	Screen nav1, nav2, nav3, nav4;
	Screen navbar;
	
	ArrayList<Hotspot> timeScreenHotspots;
	
	JTextField textField;
	SmoothMoves animation;
	
	// An array for holding the bus schedules that are displayed at this station.
	ArrayList<BusSchedule> schedules = new ArrayList<BusSchedule>();

	// Variables to hold the special fonts.
	Font vegurR, vegurM, agencyFB;

	// The screen will refresh every 1 sec (1000 ms), to display the clock and update the bus
	// schedules.
	private static final int TIME_SCREEN_DELAY = 1000;
	private static final int ANIMATION_REFRESH = 100;
	
	// The timer for refreshing the display. When it ticks, the actionPerformed method will be
	// invoked.
	Timer refershTimeScreenTimer = new Timer(TIME_SCREEN_DELAY, this);
	Timer animationRefreshTimer = new Timer(ANIMATION_REFRESH, this);
	
	// A variable for implementing the blinking effect of the clock.
	int blinker = 0;

	// A variable for keeping track of which bus is selected on the time and map screen.
	BusSchedule selectedBus;
	// Keeping track of which day is selected to display the time table.
	int selectedDay; // 0 for weekdays, 1 for Saturday, 2 for Sunday.
	
	public BrikPanel() {
		// Set up listener
		addMouseListener(this);
		setPreferredSize(new Dimension(1014, 709));
		
		setLayout(null);
		
		textField = new JTextField();
		textField.setVisible(false);
		textField.addActionListener(this);
		textField.setBounds(392, 93,1024-392, 50);
		add(textField);
		
		animation = new SmoothMoves();
		add(animation);
		
		// Initialize time screen hotspots
		timeScreenHotspots = new ArrayList<Hotspot>();
		timeScreenHotspots.add(new Hotspot(130, 290, 1024, 384, null));
		timeScreenHotspots.add(new Hotspot(130, 385, 1024, 479, null));
		timeScreenHotspots.add(new Hotspot(130, 480, 1024, 574, null));

		// Initialize navbars (default position 0,0)
		nav1 = new Screen("imgs/menutest_s1.png");
		nav2 = new Screen("imgs/menutest_s2.png");
		nav3 = new Screen("imgs/menutest_s3.png");
		nav4 = new Screen("imgs/menutest_s4.png");

		// Initialize screens
		scr1 = new TimeScreen();
		scr2 = new BusScreen();
		scr3 = new MapScreen(animation); // Set with animation handle
		scr4 = new SearchScreen(this);

		// Set positions
		scr1.setPosition(129, 0);
		scr2.setPosition(129, 0);
		scr3.setPosition(129, 0);
		scr4.setPosition(129, 0);

		// Define navbar hotspots
		nav1.addHotspot(new Hotspot(0, 91, 112, 203, scr1, nav1));
		nav1.addHotspot(new Hotspot(0, 233, 112, 345, scr2, nav2));
		// nav1.addHotspot(new Hotspot(91, 0 , 203, 112, scr1, nav1));
		// nav1.addHotspot(new Hotspot(233, 0, 345, 112, scr2, nav2));
		nav1.addHotspot(new Hotspot(0, 378, 112, 490, scr3, nav3));
		nav1.addHotspot(new Hotspot(0, 522, 112, 634, scr4, nav4));

		nav2.addHotspot(new Hotspot(0, 91, 112, 203, scr1, nav1));
		nav2.addHotspot(new Hotspot(0, 233, 112, 345, scr2, nav2));
		// nav2.addHotspot(new Hotspot(91, 0 , 203, 112, scr1, nav1));
		// nav2.addHotspot(new Hotspot(233, 0, 345, 112, scr2, nav2));
		nav2.addHotspot(new Hotspot(0, 378, 112, 490, scr3, nav3));
		nav2.addHotspot(new Hotspot(0, 522, 112, 634, scr4, nav4));

		nav3.addHotspot(new Hotspot(0, 91, 112, 203, scr1, nav1));
		nav3.addHotspot(new Hotspot(0, 233, 112, 345, scr2, nav2));
		nav3.addHotspot(new Hotspot(0, 378, 112, 490, scr3, nav3));
		nav3.addHotspot(new Hotspot(0, 522, 112, 634, scr4, nav4));

		nav4.addHotspot(new Hotspot(0, 91, 112, 203, scr1, nav1));
		nav4.addHotspot(new Hotspot(0, 233, 112, 345, scr2, nav2));
		nav4.addHotspot(new Hotspot(0, 378, 112, 490, scr3, nav3));
		nav4.addHotspot(new Hotspot(0, 522, 112, 634, scr4, nav4));
		//
		// Set initial screen
		setScreen(scr1);
		setNavbar(nav1);
		
		// Start animation timer
		animationRefreshTimer.start();

		// Read bus schedules from the buses.txt file.
		readBusSchedules();

		// load special fonts for later use.
		loadFonts();

		// Initially, Default to the first bus.
		selectedBus = schedules.get(0);

		selectedDay = 0;
	}

	// load special fonts for later use.
	private void loadFonts() {

		// This code for loading fonts is adapted from
		// http://www.ritter.biz/en/programming/java/14-wie-verwende-ich-eigene-schriftarten-in-meinen-java-anwendungen
		try {

			InputStream is = new BufferedInputStream(new FileInputStream(
					"./font/Vegur-r_0.602.ttf"));

			vegurR = Font.createFont(Font.TRUETYPE_FONT, is);

			is = new BufferedInputStream(new FileInputStream(
					"./font/Vegur-m_0602.ttf"));

			vegurM = Font.createFont(Font.TRUETYPE_FONT, is);

			is = new BufferedInputStream(new FileInputStream(
					"font/AGENCY FB.TTF"));
			agencyFB = Font.createFont(Font.TRUETYPE_FONT, is);

		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	// Draw the header on the bus, map, and search screen, which shows the clock and the arrival time
	// for the next bus.
	private void drawSmallTimeScreen(Graphics g) {
		// Get the current time.
		Calendar now = Calendar.getInstance();

		// The time will be displayed in format such as 10:35 AM.
		DateFormat dfm = new SimpleDateFormat("hh:mm a");
		
		// Set antialiasing for the text.
		((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		
		// Draw the clock
		g.setFont(vegurM.deriveFont(60f));
		g.setColor(Color.white);
		// The clock text is split into two components: Time and AM/PM.
		String[] times = dfm.format(now.getTime()).split(" ");
		// Draw time
		g.drawString(times[0], 17, 53);

		// Draw AM/PM
		g.setFont(vegurR.deriveFont(35f));
		g.drawString(times[1], 165, 53);

		// Cover the colon in the time text every other second to implement the blinking effect.  
		if (blinker == 0) {
			g.setColor(new Color(78, 78, 73));
			g.fillRect(86, 22, 8, 34);
			g.setColor(Color.white);
		}

		// Draw the date of today. In format as Friday, May 23.
		dfm = new SimpleDateFormat("EEEE, MMM dd");
		g.setFont(vegurR.deriveFont(25f));
		g.drawString(dfm.format(now.getTime()), 17, 79);

		// Variables to keep track which bus will come to this station next.
		long minWaitTime = 100;
		BusSchedule nextArrivalBus = null;
		// Find out which bus comes next.
		for (BusSchedule busSchedule : schedules) {

			Date arrivalTime = busSchedule.getNextArrivalTime();
			Calendar c = Calendar.getInstance();

			if (arrivalTime != null) {
				c.setTime(arrivalTime);
				// Calculate how long until the next bus's arrival.
				long waitTime = (c.getTimeInMillis() - now.getTimeInMillis()) / 1000 / 60;

				// Determine which bus arrives next.
				if (waitTime < minWaitTime) {
					minWaitTime = waitTime;
					nextArrivalBus = busSchedule;
				}
			}
		}

		// If there is at least one bus running, show its next arrival time at the right of the
		// header.
		if (nextArrivalBus != null) {
			g.setFont(vegurR.deriveFont(30f));
			g.setColor(Color.white);			
			if (minWaitTime != 0) {
				g.drawString("Arrives in", 685, 53);
				g.setColor(new Color(255, 103, 1));
				g.drawString(String.valueOf(minWaitTime), 807, 53);
	
				g.setColor(Color.white);
	
				FontMetrics fm = this.getFontMetrics(g.getFont());
				int width = fm.stringWidth(String.valueOf(minWaitTime));
				g.drawString("min", 807 + width + 6, 53);
			} else {
				g.drawString("has arrived", 685, 53);
			}

			g.setFont(vegurM.deriveFont(48f));
			g.drawString(nextArrivalBus.getBusID(), 615, 59);
		}

		// Start the timer if it is not running.
		if (!refershTimeScreenTimer.isRunning()) {
			// The timer will tick at the next second.
			int initialDelay = TIME_SCREEN_DELAY - now.get(Calendar.MILLISECOND);
			refershTimeScreenTimer.setInitialDelay(initialDelay);
			refershTimeScreenTimer.start();
		}

	}

	// Draw the time screen.
	private void drawTimeScreen(Graphics g) {
		// Get the current time.
		Calendar now = Calendar.getInstance();
		
		// The time will be displayed in format such as 10:35 AM.
		DateFormat dfm = new SimpleDateFormat("hh:mm a");
		
		// Set antialiasing for the text.
		((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		
		// Draw the clock
		g.setFont(vegurM.deriveFont(82f));
		g.setColor(Color.white);
		// The clock text is split into two components: Time and AM/PM.
		String[] times = dfm.format(now.getTime()).split(" ");
		// Draw timez3
		g.drawString(times[0], 15, 90);
		// Draw AM/PM
		g.setFont(vegurR.deriveFont(46f));
		g.drawString(times[1], 220, 90);

		// Cover the colon in the time text every other second to implement the blinking effect.  
		if (blinker == 0) {
			g.setColor(new Color(78, 78, 73));
			g.fillRect(108, 45, 18, 48);
			g.setColor(Color.white);
		}

		// Draw the date of today. In format as Friday, May 23.
		dfm = new SimpleDateFormat("EEEE, MMM dd");
		g.setFont(vegurR.deriveFont(30f));
		g.drawString(dfm.format(now.getTime()), 25, 120);

		// Variables to keep track which bus will come to this station next.
		long minWaitTime = 100;
		BusSchedule nextArrivalBus = null;
		
		g.setFont(vegurR.deriveFont(46f));
		int rowHeight = 93;
		int rowNum = 0;
		// Draw the rows that display the destination and arrival time for each bus.
		for (BusSchedule busSchedule : schedules) {
			// Draw bus ID.
			g.drawString(busSchedule.getBusID(), 40, 354 + rowNum * rowHeight);
			// Draw bus destination.
			g.drawString(busSchedule.getDestination(), 280, 354 + rowNum
					* rowHeight);

			Date arrivalTime = busSchedule.getNextArrivalTime();
			Calendar c = Calendar.getInstance();

			if (arrivalTime != null) {
				c.setTime(arrivalTime);
				long waitTime = (c.getTimeInMillis() - now.getTimeInMillis()) / 1000 / 60;

				if (waitTime == 0) {
					g.drawString("arrived", 649, 354 + rowNum * rowHeight);
				} else {
					// Draw bus's next arrival time.
					g.drawString(String.format("%d minutes", waitTime), 649, 354
							+ rowNum * rowHeight);
				}

				if (waitTime < minWaitTime) {
					minWaitTime = waitTime;
					nextArrivalBus = busSchedule;
				}
			} else {
				g.drawString("not running", 649, 354 + rowNum * rowHeight);
			}

			rowNum++;
		}

		// If there is at least one bus running, show its next arrival time at the right of the
		// header.
		if (nextArrivalBus != null) {
			g.setColor(Color.black);

			if (minWaitTime != 0) {
				g.drawString("Arrives in", 514, 135);
				g.setColor(new Color(255, 103, 1));
				g.drawString(String.valueOf(minWaitTime), 698, 135);
	
				g.setColor(Color.black);
	
				FontMetrics fm = this.getFontMetrics(g.getFont());
				int width = fm.stringWidth(String.valueOf(minWaitTime));
				g.drawString("min", 698 + width + 10, 135);
			} else {
				g.drawString("has arrived", 530, 135);
			}

			g.setColor(Color.white);
			g.drawString(nextArrivalBus.getDestination(), 550, 190);

			g.setFont(vegurM.deriveFont(140f));
			g.drawString(nextArrivalBus.getBusID(), 340, 162);
		}

		// Start the timer if it is not running.
		if (!refershTimeScreenTimer.isRunning()) {
			int initialDelay = TIME_SCREEN_DELAY - now.get(Calendar.MILLISECOND);
			refershTimeScreenTimer.setInitialDelay(initialDelay);
			refershTimeScreenTimer.start();
		}

	}

	// Load bus schedules from buses.txt file.
	private void readBusSchedules() {
		StringBuffer fileData = new StringBuffer();
		BufferedReader reader = null;

		try {
			// Open the file
			reader = new BufferedReader(new FileReader("./data/buses.txt"));
			char[] buf = new char[1024];
			int numRead = 0;
			// Read all data into readData.
			while ((numRead = reader.read(buf)) != -1) {
				String readData = String.valueOf(buf, 0, numRead);
				fileData.append(readData);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (reader != null) {
					reader.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		// Break the data into lines.
		String[] lines = fileData.toString().split("\n");

		int i = 0;
		BusSchedule busSchedule = null;
		// Parse the lines 
		while (i < lines.length) {
			String text = lines[i].trim();
			if (text.startsWith("Bus")) {// This line marks the start of one bus schedule
				busSchedule = new BusSchedule();
				schedules.add(busSchedule);
				// Save the bus ID
				String[] desc = text.split(":");
				busSchedule.setBusID(desc[0].substring(4, desc[0].length()));
				busSchedule.setDestination(desc[1]);
			}

			if (busSchedule != null) {  // Start to save schedules into the current busSchedule variable.
				if (text.equals("Monday to Friday")) { // Save to weekdays array.
					String timeText = lines[++i];
					while (Pattern.matches("\\d\\d:\\d\\d [AP]M", timeText)) {
						// The schedules should be in 10:53 AM format.
						DateFormat dfm = new SimpleDateFormat("hh:mm a");
						try {
							busSchedule.getWeekdays()
									.add((dfm.parse(timeText)));
						} catch (ParseException e) {
							e.printStackTrace();
						}
						if (i + 1 < lines.length)
							timeText = lines[++i];
						else
							break;
					}

					--i;
				} else if (text.equals("Saturday")) { // Save to Saturday array.
					String timeText = lines[++i];
					while (Pattern.matches("\\d\\d:\\d\\d [AP]M", timeText)) {
						DateFormat dfm = new SimpleDateFormat("hh:mm a");
						try {
							busSchedule.getSat().add(dfm.parse(timeText));
						} catch (ParseException e) {
							e.printStackTrace();
						}
						if (i + 1 < lines.length)
							timeText = lines[++i];
						else
							break;
					}

					--i;
				} else if (text.equals("Sunday")) {  // Save to Sunday array.
					String timeText = lines[++i];
					while (Pattern.matches("\\d\\d:\\d\\d [AP]M", timeText)) {
						DateFormat dfm = new SimpleDateFormat("hh:mm a");
						try {
							busSchedule.getSun().add(dfm.parse(timeText));
						} catch (ParseException e) {
							e.printStackTrace();
						}
						if (i + 1 < lines.length)
							timeText = lines[++i];
						else
							break;
					}

					--i;
				}
			}
			i++;
		}
	}
	
	public void showSearchBox()
	{
		// Show text field
		textField.setVisible(true);
        textField.requestFocusInWindow();
		revalidate();
		repaint();
	}
	
	public void hideSearchBox()
	{
		// Hide text field
		textField.setVisible(false);
		revalidate();
		repaint();
	}

	private void setScreen(Screen screen) {
		if (screen == scr4)
		{
			SearchScreen s = (SearchScreen)scr4;
			s.reset();
			showSearchBox();
		}
		else
		{
			hideSearchBox();
		}
		animation.setActive(false);
		currScreen = screen;
	}

	private void setNavbar(Screen screen) {
		navbar = screen;
	}

	public void mouseReleased(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();

		if (currScreen == scr1)
		{
			// Check timescreen hotspots
			for (int i=0; i<timeScreenHotspots.size(); ++i)
			{
				Hotspot h = timeScreenHotspots.get(i);
				if (h.contains(x, y))
				{
					BusScreen b = (BusScreen) scr2;
					b.selectBus(i);
					setNavbar(nav2);
					setScreen(scr2);
				}
			}
		}
		else
		{
			// Check current screen hotspots
			currScreen.checkClick(x, y);
		}

		// Check mouse click against hotspot areas for navbar
		ArrayList<Hotspot> hotspots = navbar.getHotspots();
		for (int i = 0; i < hotspots.size(); ++i) {
			Hotspot h = hotspots.get(i);
			if (h.contains(x, y)) {
				setNavbar(h.navbar);
				setScreen(h.screen);
			}
		}
		repaint();
	}
	
	public void searchToMap()
	{
		setScreen(scr3);
		setNavbar(nav3);
		MapScreen m = (MapScreen)scr3;
		m.searchToMap();
	}

	// Dummy overrides
	public void mouseClicked(MouseEvent e) {
	}

	public void mousePressed(MouseEvent e) {
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	// Override the paint method
	@Override
	public void paintComponent(Graphics g) {
		// Clear
		g.clearRect(0, 0, 640, 480);

		// Draw navbar
		g.drawImage(navbar.getBackground(), 0, 0, null);

		// Draw current screen
		currScreen.draw(g);

		// Set coordinates to the right display.
		g.translate(126, 0);

		if (currScreen == scr1) {// Draw the time screen

			drawTimeScreen(g);
		} else { // If it is not the time screen, then stop refreshing.
			refershTimeScreenTimer.stop();

			if (currScreen == scr2) {

				drawScheduleScreen(g);

				drawSmallTimeScreen(g); // Show small time on the top of the
										// window
			}
		}

		if (currScreen == scr3) {

			// drawScheduleScreen(g);

			drawSmallTimeScreen(g); // Show small time on the top of the window
		}

		if (currScreen == scr4) {

			// drawScheduleScreen(g);

			drawSmallTimeScreen(g); // Show small time on the top of the window
		}
		g.translate(-126, 0);
		
		if (animation.isActive())
			animation.paintComponent(g);
	}
	
	private void drawScheduleScreen(Graphics g) {
		ArrayList<Date> useSchedules = null;

		BusScreen b = (BusScreen) scr2;
		selectedDay = b.getSelectedDay();
		selectedBus = schedules.get(b.getSelectedBus());

		switch (selectedDay) {
		case 0:
			useSchedules = selectedBus.getWeekdays();
			break;
		case 1:
			useSchedules = selectedBus.getSat();
			break;
		case 2:
			useSchedules = selectedBus.getSun();
			break;
		default:
			useSchedules = null;
		}

		g.setFont(vegurR.deriveFont(32f));

		if (useSchedules != null) {
			DateFormat dfm = new SimpleDateFormat("hh:mm");

			int numColumn = 6;
			int rowHeight = 50;
			int columnWidth = 92;

			int numAM = 0;
			int numPM = 0;
			
			// Get the actual arrival time.
			Calendar actual = Calendar.getInstance();
			Date nextArrivalTime = selectedBus.getNextArrivalTime();
			if (nextArrivalTime != null)
				actual.setTime(selectedBus.getNextArrivalTime());
			
			for (int i = 0; i < useSchedules.size(); i++) {
				Calendar c = Calendar.getInstance();
				Date date = useSchedules.get(i);
				c.setTime(date);
				c.set(actual.get(Calendar.YEAR), actual.get(Calendar.MONTH), actual.get(Calendar.DATE));
				((Graphics2D) g).setRenderingHint(
						RenderingHints.KEY_ANTIALIASING,
						RenderingHints.VALUE_ANTIALIAS_ON);
				
				// If it is within 3 minutes, highlight the time.
				if (nextArrivalTime != null && Math.abs(actual.getTimeInMillis() - c.getTimeInMillis()) <= 1000 * 3 * 60) {
					g.setColor(new Color(255, 103, 1));
				} else {
					g.setColor(Color.black);
				}
				
				if (c.get(Calendar.AM_PM) == Calendar.AM) {
					g.setFont(vegurM.deriveFont(38f));
					
					g.drawString(dfm.format(date), 152 + (numAM % numColumn)
							* columnWidth * 13 / 10, 260 + (numAM / numColumn)
							* rowHeight * 13 / 10);
					++numAM;
				} else {
					g.drawString(dfm.format(date), 152 + (numPM % numColumn)
							* columnWidth * 13 / 10, 515 + (numPM / numColumn)
							* rowHeight * 13 / 10);
					++numPM;
				}
			}

			// g.drawString("AM", 106, 185);
			if (numAM < useSchedules.size()){
				g.drawString("", 106, 185 + (numAM / numColumn + 1) * rowHeight);
			} else {
			((Graphics2D) g).setRenderingHint(
					RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			g.setFont(vegurM.deriveFont(38f));
			g.setColor(Color.red);
			
			if (selectedDay == 0) {
				g.drawString("This route does not operate on weekdays.", 150, 350);
			} else if (selectedDay == 1) {
				g.drawString("This route does not operate on Saturdays.", 150, 350);
			} else if (selectedDay == 2) {
				g.drawString("This route does not operate on Sundays.", 150, 350);
			}
		}
	}}

	// AWT events
	public void actionPerformed(ActionEvent arg0) {
		if (arg0.getID() == 1001)
		{
			// Text entered
			SearchScreen s = (SearchScreen)scr4;
			s.doSearch(textField.getText());
			textField.setText("");
			this.repaint();
		}
		else
		{
			if (arg0.getSource().equals(refershTimeScreenTimer))
			{
				// This method fires when the refershTimer ticks.
				blinker = (blinker + 1) % 2;
			}
			if (arg0.getSource().equals(animationRefreshTimer))
			{
				this.repaint();
			}
		}
	}
}